<?php
/***********************************************************************

  Copyright (C) 2002-2008  PunBB

  This file is part of PunBB.

  PunBB is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published
  by the Free Software Foundation; either version 2 of the License,
  or (at your option) any later version.

  PunBB is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
  MA  02111-1307  USA

************************************************************************/

//define('PUN_ROOT', './');
//require PUN_ROOT.'include/common.php';


/**
 * Checks news feeds for updates once their refresh interval has elapsed.
 */
function aggregator_do_update()
{
	global $db;
	/* Because we have no cron */
	$result = $db->query('SELECT * FROM '.$db->prefix.'feeds WHERE checked + refresh < '.time()) or error('Unable to fetch feed info', __FILE__, __LINE__, $db->error());
	while ($feed = $db->fetch_assoc($result))
	{
		aggregator_parse_feed($feed['url'], $feed['userid']);
	}
}

function aggregator_parse_feed($feedurl, $userid)
{
    global $db;
	$result = $db->query('SELECT id FROM '.$db->prefix.'feeds WHERE userid='.$userid) or error('Unable to fetch feed info', __FILE__, __LINE__, $db->error());
	$feedid = $db->result($result, 0);
	if ($feedid < 1)
		error('Unable to find feeds for specified user', __FILE__, __LINE__);

	define ('MAGPIE_OUTPUT_ENCODING', 'UTF-8');
	$num_items = 10;
	require_once('magpierss/rss_fetch.inc');
	$rss = fetch_rss($feedurl);
	if (!$rss)
		return false;

	$items = array_slice($rss->items, 0, $num_items);
	$feedlink = $rss->channel['link'];
	foreach ($items as $item)
	{
		/**
		 * Atom feeds have a CONTENT and/or SUMMARY tag instead of a DESCRIPTION tag
		 */
		if ($item['content:encoded'])
		{
			$item['description'] = $item['content:encoded'];
		}
		else if ($item['summary'])
		{
			$item['description'] = $item['summary'];
		}
		else if ($item['CONTENT'])
		{
			$item['description'] = $item['content'];
		}

		/*
		 ** Resolve the item's title. If no title is found, we use
		 ** up to 40 characters of the description ending at a word
		 ** boundary but not splitting potential entities.
		 */
		if ($item['title'])
		{
			$title = $item['title'];
		}
		else
		{
			$title = preg_replace('/^(.*)[^\w;&].*?$/', "\\1", truncate_utf8($item['description'], 40));
		}

		/*
		 ** Resolve the items link.
		 */

		if ($item['link'])
		{
			$link = $item['link'];
		}
		else
		{
			$link = $feedlink;
		}
		if ($item['guid'])
		{
			$guid = $item['guid'];
		}

		/*
		 ** Try to resolve and parse the item's publication date. If no
		 ** date is found, we use the current date instead.
		 */

		if ($item['pubdate']) $date = $item['pubdate'];                        // RSS 2.0
		else if ($item['dc:date']) $date = $item['dc:date'];                   // Dublin core
		else if ($item['dcterms:issued']) $date = $item['dcterms:issued'];     // Dublin core
		else if ($item['dcterms:created']) $date = $item['dcterms:created'];   // Dublin core
		else if ($item['dcterms:modified']) $date = $item['dcterms:modified']; // Dublin core
		else if ($item['issued']) $date = $item['issued'];                     // Atom XML
		else if ($item['created']) $date = $item['created'];                   // Atom XML
		else if ($item['modified']) $date = $item['modified'];                 // Atom XML
		else if ($item['published']) $date = $item['published'];               // Atom XML
		else if ($item['updated']) $date = $item['updated'];                   // Atom XML
		else $date = 'now';

		$timestamp = strtotime($date); // As of PHP 5.1.0, strtotime returns false on failure instead of -1.
		if ($timestamp <= 0)
		{
			$timestamp = aggregator_parse_w3cdtf($date); // Returns false on failure
			if (!$timestamp)
			{
				$timestamp = time(); // better than nothing
			}
		}

		/*
		 ** Save this item. Try to avoid duplicate entries as much as
		 ** possible. If we find a duplicate entry, we resolve it and
		 ** pass along it's ID such that we can update it if needed.
		 */

		if ($guid)
		{
			$result = $db->query('SELECT id FROM '.$db->prefix.'feeditems WHERE fid='.$feedid.' AND guid=\''.$db->escape($guid).'\'') or error('Unable to search feeditem', __FILE__, __LINE__, $db->error());
			$entry = $db->result($result, 0);
		}
		else if ($link && $link != $feedlink && $link != $feedurl)
		{
			$result = $db->query('SELECT id FROM '.$db->prefix.'feeditems WHERE fid='.$feedid.' AND link=\''.$db->escape($link).'\'') or error('Unable to search feeditem', __FILE__, __LINE__, $db->error());
			$entry = $db->result($result, 0);
		}
		else
		{
			$result = $db->query('SELECT id FROM '.$db->prefix.'feeditems WHERE fid='.$feedid.' AND title=\''.$db->escape($title).'\'') or error('Unable to search feeditem', __FILE__, __LINE__, $db->error());
			$entry = $db->result($result, 0);
		}

		$db->query('SELECT id FROM '.$db->prefix.'feeditems WHERE fid='.$feedid.' AND link=\''.$db->escape($link).'\'') or error('Unable to search feeditem', __FILE__, __LINE__, $db->error());

		if ($entry && $title)
		{
			$db->query('UPDATE '.$db->prefix.'feeditems SET title=\''.$db->escape($title).'\', link=\''.$db->escape($link).'\', author=\''.$db->escape($item['author']).'\', description=\''.$db->escape($item['description']).'\', guid=\''.$db->escape($guid).'\' WHERE id='.$entry) or error('Unable to update feeditem', __FILE__, __LINE__, $db->error());
		}
		else if ($entry)
		{
			$db->query('DELETE FROM '.$db->prefix.'feeditems WHERE id='.$entry) or error('Unable to delete feeditem', __FILE__, __LINE__, $db->error());
		}
		else if ($title && $link)
		{
			$db->query('INSERT INTO '.$db->prefix.'feeditems (fid, userid, timestamp, title, link, author, description, guid) VALUES('.$feedid.', '.$userid.', '.$timestamp.', \''.$db->escape($title).'\', \''.$db->escape($link).'\', \''.$db->escape($item['author']).'\', \''.$db->escape($item['description']).'\', \''.$db->escape($guid).'\')') or error('Unable to insert feeditem', __FILE__, __LINE__, $db->error());
		}
	}

	/*
	 ** Remove all items that are older than flush item timer:
	 */

	$age = time() - 14515200;    // 24 weeks
	$db->query('DELETE FROM '.$db->prefix.'feeditems WHERE fid='.$feedid.' AND timestamp<'.$age) or error('Unable to delete feeditem', __FILE__, __LINE__, $db->error());

	/*
	 ** Update feed's check time
	 */
	$db->query('UPDATE '.$db->prefix.'feeds SET checked='.time().' WHERE id='.$feedid) or error('Unable to update feed\'s check time', __FILE__, __LINE__, $db->error());
	return true;
}

/**
 * Parse the W3C date/time format, a subset of ISO 8601. PHP date parsing
 * functions do not handle this format.
 * See http://www.w3.org/TR/NOTE-datetime for more information.
 * Originally from MagpieRSS (http://magpierss.sourceforge.net/).
 *
 * @param $date_str A string with a potentially W3C DTF date.
 * @return A timestamp if parsed successfully or -1 if not.
 */
function aggregator_parse_w3cdtf($date_str)
{
	if (preg_match('/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(:(\d{2}))?(?:([-+])(\d{2}):?(\d{2})|(Z))?/', $date_str, $match))
	{
		list($year, $month, $day, $hours, $minutes, $seconds) = array($match[1], $match[2], $match[3], $match[4], $match[5], $match[6]);
		// calc epoch for current date assuming GMT
		$epoch = gmmktime($hours, $minutes, $seconds, $month, $day, $year);
		if ($match[10] != 'Z')
		{ // Z is zulu time, aka GMT
			list($tz_mod, $tz_hour, $tz_min) = array($match[8], $match[9], $match[10]);
			// zero out the variables
			if (!$tz_hour)
			{
				$tz_hour = 0;
			}
			if (!$tz_min)
			{
				$tz_min = 0;
			}
			$offset_secs = (($tz_hour * 60) + $tz_min) * 60;
			// is timezone ahead of GMT?  then subtract offset
			if ($tz_mod == '+')
			{
				$offset_secs *= -1;
			}
			$epoch += $offset_secs;
		}
		return $epoch;
	}
	else
	{
		return false;
	}
}

/**
 * Truncate a UTF-8-encoded string safely to a number of bytes.
 *
 * If the end position is in the middle of a UTF-8 sequence, it scans backwards
 * until the beginning of the byte sequence.
 *
 * Use this function whenever you want to chop off a string at an unsure
 * location. On the other hand, if you're sure that you're splitting on a
 * character boundary (e.g. after using strpos() or similar), you can safely use
 * substr() instead.
 *
 * @param $string
 *   The string to truncate.
 * @param $len
 *   An upper limit on the returned string length.
 * @param $wordsafe
 *   Flag to truncate at nearest space. Defaults to false.
 * @return
 *   The truncated string.
 */
function truncate_utf8($string, $len, $wordsafe = false, $dots = false) {
	$slen = strlen($string);
	if ($slen <= $len) {
		return $string;
	}
	if ($wordsafe) {
		$end = $len;
		while (($string[--$len] != ' ') && ($len > 0)) {};
		if ($len == 0) {
			$len = $end;
		}
	}
	if ((ord($string[$len]) < 0x80) || (ord($string[$len]) >= 0xC0)) {
		return substr($string, 0, $len) . ($dots ? ' ...' : '');
	}
	while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0) {};
	return substr($string, 0, $len) . ($dots ? ' ...' : '');
}

